﻿/**********************************************************************\
*
* This file is part of EMV Cardreader, a software Mastercard/VISA
* CAP/DPA implementation - https://code.google.com/p/emv-cardreader-sec
* For Brevity, EMV Cardreader (the software & all source code) shall
* herein be referred to simply as 'EMVcr'
*
* Copyright 2011 by 'Olipro'
*
* Please consult the "LICENSE.txt" file that (should) be included
* with the source for full licensing details applicable to this software.
*
* EMVcr is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EMVcr.  If not, see <http://www.gnu.org/licenses/>.
*
\**********************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Windows.Forms;
using System.Diagnostics;
using GemCard;

namespace Cardreader_Security
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private enum CurrentState
        {
            Nothing,
            EnteringPIN,
            EnteringRef,
            EnteringAmount,
            WrongPIN,
        };
        private enum OperationMode
        {
            Nothing,
            Identify,
            Respond,
            Sign,
        }
        CurrentState curstate = CurrentState.Nothing;
        OperationMode opmode = OperationMode.Nothing;

        CardNative sc = new CardNative();
        APDUResponse APDUr;
        byte[] Data;
        bool MustSetTxnDate = true;
        byte[] CAPfilter;
        string PINstr = "", amntstr = "", refstr = "", readerglobal = "";
        ulong amntval = 0;

        private void Form1_Load(object sender, EventArgs e)
        {
            string[] readersList = sc.ListReaders();
            if (readersList == null)
            {
                MessageBox.Show("No SmartCard Readers were found connected to your computer, please run this program after you have a reader connected", "Reader not found", MessageBoxButtons.OK, MessageBoxIcon.Stop);
                Application.Exit();
                return;
            }
            sc.OnCardInserted += new CardInsertedEventHandler(sc_OnCardInserted);
            sc.OnCardRemoved += new CardRemovedEventHandler(sc_OnCardRemoved);
            foreach (string rdr in readersList)
            {
                try
                {
                    sc.StartCardEvents(rdr);
                }
                catch { }
            }
            foreach (string card in readersList)
            {
                try
                {
                    sc.Connect(card, SHARE.Exclusive, PROTOCOL.T0orT1);
                }
                catch
                {
                    continue;
                }
                try
                {
                    sc.Disconnect(DISCONNECT.Leave);
                    sc_OnCardInserted(card);
                    break;
                }
                catch { }
            }
        }

        void sc_OnCardInserted(string reader)
        {
            int res = 0;
            lock (this)
            {
                if (sc.IsConnected())
                    return;
                res = ConnectToCard(reader);

                if (res == -1)
                    this.BeginInvoke(new MethodInvoker(() => info.Text = "Connect Failed, Clean Card Chip"));
                else if (res == 0)
                    this.BeginInvoke(new MethodInvoker(() => info.Text = "Wrong Card"));
                else
                {
                    this.BeginInvoke(new MethodInvoker(() => info.Text = "Select Operation"));
                    this.BeginInvoke(new MethodInvoker(() => setButtons(true)));
                }
            }
        }

        void sc_OnCardRemoved(string reader)
        {
            lock (this)
            {
                this.BeginInvoke(new MethodInvoker(() => setButtons(false)));
                curstate = CurrentState.Nothing;
                opmode = OperationMode.Nothing;
                this.BeginInvoke(new MethodInvoker(() => info.Text = "Please Insert Card"));
                sc.Disconnect(DISCONNECT.Leave);
            }
        }
        void setButtons(bool status)
        {
            identifyBtn.Enabled = respondBtn.Enabled = signBtn.Enabled = status;
        }
        private int ConnectToCard(string Reader)
        {
            return ConnectToCard(Reader, false);
        }
        private int ConnectToCard(string Reader, bool quickconnect)
        {
            lock (this)
            {
                int tries = 0;
                while (tries++ < 10)
                {
                    try
                    {
                        sc.Connect(Reader, SHARE.Exclusive, PROTOCOL.T0orT1);
                    }
                    catch
                    {
                        System.Threading.Thread.Sleep(100);
                        continue;
                    }
                    break;
                }
                if (!sc.IsConnected())
                    return -1;
            }
            Data = new byte[] { 0xA0, 0x00, 0x00, 0x00, 0x03, 0x80, 0x02 }; //VISA DPA
            MustSetTxnDate = true;
            sc.BeginTransaction();
            APDUr = sc.Transmit(new APDUCommand(0x00, 0xA4, 0x04, 0x00, Data, 0x00)); //Select CAP
            if ((APDUr.Status & 0xFF00) != 0x6100)
            {
                MustSetTxnDate = false;
                Data = new byte[] { 0xA0, 0x00, 0x00, 0x00, 0x04, 0x80, 0x02 }; //Mastercard SecureCode AUT
                APDUr = sc.Transmit(new APDUCommand(0x00, 0xA4, 0x04, 0x00, Data, 0x00));
                if ((APDUr.Status & 0xFF00) != 0x6100)
                {
                    Data = new byte[] { 0xA0, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00 }; //I frankly don't know, probably for debug cards
                    APDUr = sc.Transmit(new APDUCommand(0x00, 0xA4, 0x04, 0x00, Data, 0x00));
                    if ((APDUr.Status & 0xFF00) != 0x6100)
                        return 0;
                }
            }
            byte dSize = (byte)(APDUr.Status & 0xFF);
            sc.Transmit(new APDUCommand(0x00, 0xC0, 0x00, 0x00, null, dSize));
            Data = new byte[] { 0x83, 0x00 };
            APDUr = sc.Transmit(new APDUCommand(0x80, 0xA8, 0x00, 0x00, Data, 0)); //Get Processing Options
            sc.Transmit(new APDUCommand(0x00, 0xC0, 0x00, 0x00, null, (byte)(APDUr.Status & 0xFF))); //GetResponse

            APDUr = sc.Transmit(new APDUCommand(0x00, 0xB2, 0x01, 0x0C, null, 0)); //Read Record (to get Length)
            APDUr = sc.Transmit(new APDUCommand(0x00, 0xB2, 0x01, 0x0C, null, (byte)(APDUr.Status & 0xFF))); //Read Record (actual read)
            sc.EndTransaction(DISCONNECT.Leave);
            byte[] records = APDUr.Data;
            //System.IO.File.WriteAllBytes("cardout", records);
            bool found = false;
            int i;
            for (i = 0; i < records.Length; i++)
                if (records[i] == 0x9F && records[i + 1] == 0x56) //Find IAI record for response masking
                {
                    found = true;
                    break;
                }
            if (!found)
                return 0;
            CAPfilter = new byte[records[i + 2]];
            Buffer.BlockCopy(records, i + 3, CAPfilter, 0, records[i + 2]);
            readerglobal = Reader;
            return 1;
        }
        private bool SendPIN(string PIN)
        {
            Data = new byte[] { 0x24, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; //second and third bytes are for PIN, 1234 would become 0x12, 0x34
            Data.SetValue((byte)Int32.Parse(PIN.Substring(0, 2), NumberStyles.AllowHexSpecifier), 1);
            Data.SetValue((byte)Int32.Parse(PIN.Substring(2, 2), NumberStyles.AllowHexSpecifier), 2);
            sc.BeginTransaction();
            APDUr = sc.Transmit(new APDUCommand(0x00, 0x20, 0x00, 0x80, Data, 255)); //Send PIN, skipping PIN entry will NOT let you make valid response codes, but feel free to try.
            sc.EndTransaction(DISCONNECT.Leave);
            if (APDUr.Status != 0x9000)
                return false;
            return true;
        }
        private byte GetPINRetriesLeft()
        {
            sc.BeginTransaction();
            APDUr = sc.Transmit(new APDUCommand(0x80, 0xCA, 0x9F, 0x17, null, 0));
            APDUr = sc.Transmit(new APDUCommand(0x80, 0xCA, 0x9F, 0x17, null, (byte)(APDUr.Status & 0xFF)));
            sc.EndTransaction(DISCONNECT.Leave);
            return APDUr.Data[3];
        }
        private uint DoEMVCAPTransaction()
        {
            return DoEMVCAPTransaction("", "");
        }
        private uint DoEMVCAPTransaction(string refnum)
        {
            return DoEMVCAPTransaction(refnum, "");
        }
        private uint DoEMVCAPTransaction(string refnum, string amount)
        {
            bool responseisEMVtagType2 = false; //Some cards return ARQC as Response Message Template Format 2
            byte[] OTP = new byte[29];
            OTP[14] = 0x80;
            int i, j;
            if(refnum.Length == 8) //usual stuff, 12345678 --> 0x12, 0x34, 0x56, 0x78
            {
                OTP[25] = Byte.Parse(refnum.Substring(0, 2), NumberStyles.AllowHexSpecifier);
                OTP[26] = Byte.Parse(refnum.Substring(2, 2), NumberStyles.AllowHexSpecifier);
                OTP[27] = Byte.Parse(refnum.Substring(4, 2), NumberStyles.AllowHexSpecifier);
                OTP[28] = Byte.Parse(refnum.Substring(6, 2), NumberStyles.AllowHexSpecifier);
            }
            if (amount.Length > 0)
            {
                ulong money = UInt64.Parse(amount, NumberStyles.AllowHexSpecifier); //performs better on a 64bit OS, but nobody on x86 would notice the difference.
                OTP[5] = (byte)(money & 0xFF);
                OTP[4] = (byte)((money >> 8) & 0xFF);
                OTP[3] = (byte)((money >> 16) & 0xFF);
                OTP[2] = (byte)((money >> 24) & 0xFF);
                OTP[1] = (byte)((money >> 32) & 0xFF);
                OTP[0] = (byte)((money >> 40) & 0xFF);
            }
            if (MustSetTxnDate)
                OTP[21] = OTP[22] = OTP[23] = 1; //Visa DPA requires TxnDate of 0x1, 0x1, 0x1
            byte[] OTPCancel = new byte[31];
            OTPCancel[0] = 0x5A;
            OTPCancel[1] = 0x33;
            Buffer.BlockCopy(OTP, 0, OTPCancel, 2, 29); //cancellation data is basically just txn data with first 2 bytes changed
            Data = OTP; //yes, this is pointless, adjust the code if you want.
            sc.BeginTransaction();
            APDUr = sc.Transmit(new APDUCommand(0x80, 0xAE, 0x80, 0x00, Data, 0)); //submit fake EMV txn
            APDUr = sc.Transmit(new APDUCommand(0x00, 0xC0, 0x00, 0x00, null, (byte)(APDUr.Status & 0xFF))); //get card response
            if (APDUr.Data[0] == 0x77)
                responseisEMVtagType2 = true;
            byte[] CardResponse = new byte[APDUr.Data[1]]; //TLV data; second byte is length
            Buffer.BlockCopy(APDUr.Data, 2, CardResponse, 0, APDUr.Data[1]);
            Data = OTPCancel; //yeah yeah, OK, it's pointless, shut up.
            APDUr = sc.Transmit(new APDUCommand(0x80, 0xAE, 0x00, 0x00, Data, 0)); //cancel fake txn (ensures consistency of txn counter)
            sc.Transmit(new APDUCommand(0x00, 0xC0, 0x00, 0x00, null, (byte)(APDUr.Status&0xFF))); //empty card's response buffer
            sc.EndTransaction(DISCONNECT.Reset);
            sc.Disconnect(DISCONNECT.Leave);
            if (responseisEMVtagType2) //type 2 response must be parsed and converted back into type 1 equivalent
            {
                int idx = 0;
                byte[] Null = new byte[0];
                byte[] CID = Null, ATC = Null, AC = Null, IAD = Null;
                while (idx < CardResponse.Length)
                {
                    ushort EMVtag = CardResponse[idx++];
                    if ((EMVtag & 0x1F) == 0x1F) //2 byte tag has lower 5 bits of first byte set to 1 (0x1F), otherwise, tag is only 1 byte
                        EMVtag = (ushort)((EMVtag << 8) | CardResponse[idx++]);
                    //ushort EMVtag = (ushort)((CardResponse[idx] << 8) | CardResponse[idx + 1]);
                    byte len = CardResponse[idx++]; //technically, length field can be > 1 byte (MSB is 1) but that should never happen.
                    switch (EMVtag)
                    {
                        case 0x9F27:
                            CID = new byte[len];
                            Buffer.BlockCopy(CardResponse, idx, CID, 0, len);
                            break;
                        case 0x9F36:
                            ATC = new byte[len];
                            Buffer.BlockCopy(CardResponse, idx, ATC, 0, len);
                            break;
                        case 0x9F26:
                            AC = new byte[len];
                            Buffer.BlockCopy(CardResponse, idx, AC, 0, len);
                            break;
                        case 0x9F10:
                            IAD = new byte[len];
                            Buffer.BlockCopy(CardResponse, idx, IAD, 0, len);
                            break;
                    }
                    idx += len;
                }
                CardResponse = new byte[CID.Length + ATC.Length + AC.Length + IAD.Length];
                Buffer.BlockCopy(CID, 0, CardResponse, 0, CID.Length);
                Buffer.BlockCopy(ATC, 0, CardResponse, CID.Length, ATC.Length);
                Buffer.BlockCopy(AC, 0, CardResponse, CID.Length+ATC.Length, AC.Length);
                Buffer.BlockCopy(IAD, 0, CardResponse, CID.Length + ATC.Length + AC.Length, IAD.Length);
            }
            int Result = j = 0;
            for (i = CAPfilter.Length - 1; i >= 0; i--) //Number is Big Endian, start from LSB
            {
                if (CAPfilter[i] == 0)
                    continue; //if filter byte is null, nothing to do, move on
                for (int k = 0; k < 8; k++) //iterate over all bits in the byte
                    if ((CAPfilter[i] & (1 << k)) != 0) //check if bit is to be kept
                        Result |= ((CardResponse[i] & (1 << k)) >> k) << j++; //yes, we're keeping it, insert the bit above the previous one.
            }
            if (copycodechk.Checked)
                try
                {
                    Clipboard.SetText(Result.ToString());
                }
                catch { }
            opmode = OperationMode.Nothing;
            curstate = CurrentState.Nothing;
            ConnectToCard(readerglobal);
            return (uint)Result;
        }
        private void DoIdentify()
        {
            if (!SendPIN(PINstr))
            {
                info.Text = "Wrong PIN > " + GetPINRetriesLeft().ToString() + " Tries Left";
                curstate = CurrentState.WrongPIN;
                PINstr = "";
                return;
            }
            info.Text = "Code > " + DoEMVCAPTransaction().ToString();
        }
        private void DoRespond()
        {
            info.Text = "Code > " + DoEMVCAPTransaction(refstr).ToString();
        }
        private void DoSign()
        {
            info.Text = "Code > " + DoEMVCAPTransaction(refstr, amntstr).ToString();
        }
        private void StartCAPTransaction(OperationMode op)
        {
            if (GetPINRetriesLeft() == 0)
            {
                info.Text = "PIN BLOCKED";
                return;
            }
            PINstr = "";
            amntstr = "";
            amntval = 0;
            refstr = "";
            info.Text = "Enter PIN > ";
            curstate = CurrentState.EnteringPIN;
            opmode = op;
        }
#region Boring crap related to the GUI
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            sc.StopCardEvents();
            sc.OnCardInserted -= new CardInsertedEventHandler(sc_OnCardInserted);
            sc.OnCardRemoved -= new CardRemovedEventHandler(sc_OnCardRemoved);
            try
            {
                sc.Disconnect(DISCONNECT.Reset);
            }
            catch { }
        }
        private void identifyBtn_Click(object sender, EventArgs e)
        {
            StartCAPTransaction(OperationMode.Identify);
        }
        private void signBtn_Click(object sender, EventArgs e)
        {
            StartCAPTransaction(OperationMode.Sign);
        }

        private void respondBtn_Click(object sender, EventArgs e)
        {
            StartCAPTransaction(OperationMode.Respond);
        }
        private void Form1_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)System.Windows.Forms.Keys.Return || e.KeyChar == (char)System.Windows.Forms.Keys.Enter)
            {
                btnOK_Click(null, null);
                return;
            }
            if (e.KeyChar == (char)System.Windows.Forms.Keys.Back || e.KeyChar == (char)System.Windows.Forms.Keys.Delete)
            {
                btnClear_Click(null, null);
                return;
            }
            if (e.KeyChar == (char)System.Windows.Forms.Keys.Escape)
            {
                btnCancel_Click(null, null);
                return;
            }
            if(e.KeyChar < '0' || e.KeyChar > '9')
                return;
            switch (curstate)
            {
                case CurrentState.EnteringPIN:
                    if (PINstr.Length == 4)
                        return;
                    PINstr += e.KeyChar;
                    info.Text += "*";
                    break;
                case CurrentState.EnteringRef:
                    if (refstr.Length == 8)
                        return;
                    info.Text += e.KeyChar;
                    refstr += e.KeyChar;
                    break;
                case CurrentState.EnteringAmount:
                    if (amntval.ToString("D").Length == 12)
                        return;
                    amntval *= 10;
                    amntval += UInt32.Parse(e.KeyChar.ToString());
                    amntstr = amntval.ToString("D");
                    if (amntstr.Length < 3)
                        if(amntstr.Length == 1)
                            amntstr = "0.0" + amntstr;
                        else
                            amntstr = "0." + amntstr;
                    else
                        amntstr = amntstr.Insert(amntstr.Length - 2, ".");
                    info.Text = "Enter Amount > " + amntstr;
                    break;
                case CurrentState.WrongPIN:
                    info.Text = "Enter PIN > ";
                    PINstr = "";
                    curstate = CurrentState.EnteringPIN;
                    PINstr += e.KeyChar;
                    info.Text += "*";
                    break;
            }
        }

        private void one_Click(object sender, EventArgs e)
        {
            Button btn = (Button)sender;
            KeyPressEventArgs ek = new KeyPressEventArgs(btn.Text[0]);
            Form1_KeyPress(null, ek);
        }

        private void btnOK_Click(object sender, EventArgs e)
        {
            switch (curstate)
            {
                case CurrentState.EnteringPIN:
                    if (PINstr.Length != 4)
                        return;
                    if (opmode == OperationMode.Identify)
                        DoIdentify();
                    else
                    {
                        if (!SendPIN(PINstr))
                        {
                            info.Text = "Wrong PIN > " + GetPINRetriesLeft().ToString() + " Tries Left, Push OK to retry.";
                            curstate = CurrentState.WrongPIN;
                            PINstr = "";
                            return;
                        }
                        info.Text = "Enter Ref > ";
                        curstate = CurrentState.EnteringRef;
                    }
                    break;
                case CurrentState.EnteringRef:
                    refstr = Int32.Parse(refstr).ToString("00000000");
                    if(refstr == "00000000" && opmode != OperationMode.Sign)
                        MessageBox.Show("WARNING: You have entered a ref of 00000000 - this is the equivalent of using Identify, someone may be trying to trick you!", "SCAM WARNING!", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    if (opmode == OperationMode.Respond)
                        DoRespond();
                    else
                    {
                        info.Text = "Enter Amount > 0.00";
                        curstate = CurrentState.EnteringAmount;
                    }
                    break;
                case CurrentState.EnteringAmount:
                    if(amntval == 0 && refstr == "00000000")
                        MessageBox.Show("WARNING: You have entered a ref of 00000000 and an amount of 0.00 - this is the equivalent of using Identify, someone may be trying to trick you!", "SCAM WARNING!", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    else if(amntval == 0)
                        MessageBox.Show("WARNING: You have entered an amount of 0.00 - this is the equivalent of using Respond, someone may be trying to trick you!", "SCAM WARNING!", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    amntstr = amntval.ToString("D");
                    DoSign();
                    break;
                case CurrentState.WrongPIN:
                    info.Text = "Enter PIN > ";
                    PINstr = "";
                    curstate = CurrentState.EnteringPIN;
                    break;
            }
        }

        private void btnClear_Click(object sender, EventArgs e)
        {
            switch (curstate)
            {
                case CurrentState.EnteringPIN:
                    if (PINstr.Length == 0)
                        return;
                    PINstr = PINstr.Remove(PINstr.Length - 1);
                    info.Text = info.Text.Remove(info.Text.Length - 1);
                    break;
                case CurrentState.EnteringRef:
                    if (refstr.Length == 0)
                        return;
                    refstr = refstr.Remove(refstr.Length - 1);
                    info.Text = info.Text.Remove(info.Text.Length - 1);
                    break;
                case CurrentState.EnteringAmount:
                    amntval /= 10;
                    amntstr = amntval.ToString("D");
                    if (amntstr.Length < 3)
                        if (amntstr.Length == 1)
                            amntstr = "0.0" + amntstr;
                        else
                            amntstr = "0." + amntstr;
                    else
                        amntstr = amntstr.Insert(amntstr.Length - 2, ".");
                    info.Text = "Enter Amount > " + amntstr;
                    break;
            }
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            switch (curstate)
            {
                case CurrentState.EnteringPIN:
                    PINstr = "";
                    info.Text = "Enter PIN > ";
                    break;
                case CurrentState.EnteringRef:
                    refstr = "";
                    info.Text = "Enter Ref > ";
                    break;
                case CurrentState.EnteringAmount:
                    amntval = 0;
                    info.Text = "Enter Amount > 0.00";
                    break;
            }
        }

        private void info_Leave(object sender, EventArgs e)
        {
            info.Focus();
            info.SelectionLength = 0;
        }

        private void Form1_Activated(object sender, EventArgs e)
        {
            info.SelectionLength = 0;
        }

        private void notifyIcon1_MouseClick(object sender, MouseEventArgs e)
        {
            if (e.Button != System.Windows.Forms.MouseButtons.Left)
                return;
            if (this.Visible)
                this.Hide();
            else
                this.Show();
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            notifyIcon1.Visible = false;
        }

        private void checkBox1_CheckedChanged(object sender, EventArgs e)
        {
            if (checkBox1.Checked)
            {
                this.TopMost = true;
                alwaysOnTopToolStripMenuItem.Checked = true;
            }
            else
            {
                this.TopMost = false;
                alwaysOnTopToolStripMenuItem.Checked = false;
            }
        }

        private void alwaysOnTopToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
        {
            if (alwaysOnTopToolStripMenuItem.Checked)
            {
                this.TopMost = true;
                checkBox1.Checked = true;
            }
            else
            {
                this.TopMost = false;
                checkBox1.Checked = false;
            }
        }
#endregion
    }
}
